/*   The MIT License
 *   
 *   Carina Maya Exporter
 *   Copyright (c) 2010-2011 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "ces-exporter.hh"

#include <cassert>
#include <algorithm>
#include <cmath>

#include <maya/MItDag.h>
#include <maya/MIntArray.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MFloatPointArray.h>
#include <maya/MGlobal.h>
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>
#include <maya/MFnMesh.h>
#include <maya/MDagPathArray.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MFnTransform.h>
#include <maya/MItGeometry.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MMatrix.h>
#include <maya/MAnimUtil.h>
#include <maya/MFnMatrixData.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnAnimCurve.h>
#include <maya/MAnimControl.h>
#include <maya/MFnAttribute.h>
#include <maya/MQuaternion.h>
#include <maya/MVector.h>
#include <maya/MFnNumericAttribute.h>

bool CarinaMayaTranslator::haveReadMethod() const
{
    return true;
}

MStatus CarinaMayaTranslator::reader(const MFileObject &file, const MString &optionsString, FileAccessMode mode)
{
    return MS::kFailure;
}

bool CarinaMayaTranslator::canBeOpened() const
{
    return true;
}

bool CarinaMayaTranslator::haveWriteMethod() const
{
    return true;
}

MStatus CarinaMayaTranslator::writer(const MFileObject &file, const MString &optionsString, FileAccessMode mode)
{
    MString filename = file.resolvedFullName();

    CESFile ces_file;

    MStatus err;
    switch(mode)
    {
    case MPxFileTranslator::kExportAccessMode: 
        err = exportAll(ces_file); break;
    case MPxFileTranslator::kExportActiveAccessMode: 
        err = exportSelection(ces_file); break;
    default: return MS::kFailure;
    }
    CHECK_MSTATUS_AND_RETURN_IT(err);

    if(!ces_file.save(filename.asChar()))
    {
        MGlobal::displayError(filename + ": cannot be opened for writing");
        return MS::kFailure;
    }

    return MS::kSuccess;
}

MString CarinaMayaTranslator::defaultExtension() const
{
    return MString("ces");
}

MString CarinaMayaTranslator::filter() const
{
    return MString("*.ces");
}

MPxFileTranslator::MFileKind CarinaMayaTranslator::identifyFile(const MFileObject &file, const char *buffer, short size) const
{
    if(size < sizeof(uint32) || 
       reinterpret_cast<const CESHeader*>(buffer)->magic_number != ces_magic_number)
        return MPxFileTranslator::kNotMyFileType;
    return MPxFileTranslator::kIsMyFileType;    
}

MStatus CarinaMayaTranslator::exportAll(CESFile& ces_file)
{
    MStatus err;
    MItDag iter(MItDag::kBreadthFirst, MFn::kMesh, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);

    for(; !iter.isDone(); iter.next())
    {
        MObject obj = iter.currentItem(&err);
        CHECK_MSTATUS_AND_RETURN_IT(err);

        MFnDagNode node(obj, &err);
        if(node.isIntermediateObject(&err))
            continue;
        CHECK_MSTATUS_AND_RETURN_IT(err);

        MString name = node.name(&err);
        CHECK_MSTATUS_AND_RETURN_IT(err);

        CESMeshInPlacePtr mesh_info = make_aligned_shared<CESMeshInPlace>();
        err = readMesh(mesh_info->getMesh(), obj);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        ces_file.getGeometrySection().addMeshReference(name.asChar(), mesh_info);

        // Export the tree correctly
        CESEntity& model = ces_file.getEntitySection().addEntity();
        model.setAttribute("Name", name.asChar());
        model.setAttribute("Rendering mesh", name.asChar());
        model.setAttribute("Collision hierarchy", "generate");

        CESNode& ces_node = ces_file.getRoot().addChild();
        ces_node.attributes().setAttribute("Node type", "Model node");
        ces_node.attributes().setAttribute("Creation data", name.asChar());
    }

    return MS::kSuccess; 
}

MStatus CarinaMayaTranslator::exportSelection(CESFile& ces_file)
{
    MStatus err;
    MSelectionList sl;

    err = MGlobal::getActiveSelectionList(sl);
    CHECK_MSTATUS_AND_RETURN_IT(err);
    MItSelectionList iter(sl, MFn::kMesh, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);

    for(; !iter.isDone(); iter.next())
    {
        MObject obj;
        err = iter.getDependNode(obj);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        
        MFnDagNode node(obj, &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        if(node.isIntermediateObject(&err))
            continue;
        CHECK_MSTATUS_AND_RETURN_IT(err);

        MString name = node.name(&err);
        CHECK_MSTATUS_AND_RETURN_IT(err);

        CESMeshInPlacePtr mesh_info = make_aligned_shared<CESMeshInPlace>();
        err = readMesh(mesh_info->getMesh(), obj);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        ces_file.getGeometrySection().addMeshReference(name.asChar(), mesh_info);

        // Export the tree correctly
        CESEntity& model = ces_file.getEntitySection().addEntity();
        model.setAttribute("Name", name.asChar());
        model.setAttribute("Rendering mesh", name.asChar());
        model.setAttribute("Collision hierarchy", "generate");

        CESNode& ces_node = ces_file.getRoot().addChild();
        ces_node.attributes().setAttribute("Node type", "Model node");
        ces_node.attributes().setAttribute("Creation data", name.asChar());
    }
    return MS::kSuccess;
}

MStatus CarinaMayaTranslator::readSkin(DataVector<CESSkinData>& skin_data, MObject& skin_obj, MObject& mesh_obj)
{
    MStatus         err;
    CESSkinData     data;
    MDagPathArray   joints;
    MFnSkinCluster  fn_skin(skin_obj, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);

    MFnDagNode      mesh_node(mesh_obj, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);
    MDagPath        mesh_path;
    err = mesh_node.getPath(mesh_path);
    CHECK_MSTATUS_AND_RETURN_IT(err);

    size_t          infl_count;
    MDoubleArray    weights;
    const float     weight_epsilon = 1e-3f;

    MFnMesh mesh(mesh_obj);
    MItMeshPolygon poly_iter(mesh_obj, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);
    skin_data.resize(mesh.numVertices());
    for(; !poly_iter.isDone(); poly_iter.next())
    {
        MItGeometry vert_iter(mesh_path, poly_iter.currentItem(), &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        for(; !vert_iter.isDone(); vert_iter.next())
        {
            err = fn_skin.getWeights(mesh_path, vert_iter.currentItem(), weights, infl_count);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            float total_weight = 0;
            size_t w = 0;
            for(size_t k = 0; k < infl_count; ++k)
            {
                float weight = (float)weights[k];

                if(weight)
                {
                    if(w == ces_max_inf_bones)
                    {
                        size_t idx = std::min_element(data.weight, data.weight+ces_max_inf_bones) - data.weight;
                        float correction;
                        if(data.weight[idx] < weight)
                        {
                            correction = data.weight[idx]/4.0f;
                            data.boneid[idx] = (float)k;
                            data.weight[idx] = weight;
                        }
                        else
                            correction = weight/4.0f;
                        for(size_t m = 0; m < ces_max_inf_bones; ++m)
                            data.weight[m] += correction;
                    }
                    else
                    {
                        data.boneid[w] = (float)k;
                        data.weight[w] = weight;
                        ++w;
                    }
                }

                total_weight += weight;
            }
            if(fabs(total_weight-1.0f) > weight_epsilon)
                return MS::kFailure;
            for(; w < ces_max_inf_bones; ++w)
                data.boneid[w] = 0.0f, data.weight[w] = 0.0f;
            skin_data[vert_iter.index()] = data;
        }
    }

    return MS::kSuccess;
}

MStatus CarinaMayaTranslator::readMaterial(CESMesh& ces_mesh, const MIntArray& poly_vert_count, MObject& obj) 
{
    size_t          sm_count = 0,
                    sm_offset = 0;
    MStatus         err;
    MObjectArray    shaders;
    MIntArray       sidx;
    MFnMesh         mesh(obj, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);
    mesh.getConnectedShaders(0, shaders, sidx);
    assert(sidx.length() != 0);
    {
    CESSubmesh* sm = &ces_mesh.addSubmesh();
    for(size_t i = 0; i < sidx.length(); ++i)
    {
        if(i != 0)
        {
            if(sidx[i-1] > sidx[i])
                return MS::kFailure; // WARNING: Sorting is not supported
            if(sidx[i-1] != sidx[i])
            {
                sm->setCount(sm_count);
                sm = &ces_mesh.addSubmesh();
                sm_offset += sm_count;
                sm->setOffset(sm_offset);
                sm_count = 0;
            }
        }
        sm_count += poly_vert_count[i]*3 - 6;
    }
    sm->setCount(sm_count);
    }

    for(size_t i = 0; i < shaders.length(); ++i)
    {
        CESSubmesh& sm = ces_mesh.getSubmesh(i);
        CESAttributeContainer& sm_attr = sm.attributes();
        MPlugArray plug_arr;
        MFnDependencyNode snode(shaders[i]);
        MPlug sf_shader = snode.findPlug("surfaceShader", false, &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        sf_shader.connectedTo(plug_arr, true, false, &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        if(plug_arr.length() == 0 || plug_arr.length() > 1)
            return MS::kFailure;
        MFnDependencyNode shader(plug_arr[0].node(), &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        MString stn(shader.typeName());
        if(stn == "phong")
        {
            vec4_t  spec_val;
            float   sh_val, spec_int_val;

            MPlug specular = shader.findPlug("miSpecularColor", false, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            MPlug shininess = shader.findPlug("miShinyness", false, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            MPlug color = shader.findPlug("color", false, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            
            CHECK_MSTATUS_AND_RETURN_IT(err);
            err = shininess.getValue(sh_val);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            if(specular.numChildren() != 3)
                return MS::kFailure;
            err = specular.child(0).getValue(spec_val.coordinate.x);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            err = specular.child(1).getValue(spec_val.coordinate.y);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            err = specular.child(2).getValue(spec_val.coordinate.z);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            spec_int_val = 0.299f*spec_val.coordinate.x + 
                           0.587f*spec_val.coordinate.y +
                           0.114f*spec_val.coordinate.z;

            MPlugArray color_plugs;
            color.connectedTo(color_plugs, true, false, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            if(color_plugs.length() > 1)
                return MS::kFailure;

            if(color_plugs.length() == 0)
            {
                vec4_t  color_val;

                sm.setMaterial("mesh_simple");

                err = color.child(0).getValue(color_val.coordinate.x);
                CHECK_MSTATUS_AND_RETURN_IT(err);
                err = color.child(1).getValue(color_val.coordinate.y);
                CHECK_MSTATUS_AND_RETURN_IT(err);
                err = color.child(2).getValue(color_val.coordinate.z);
                CHECK_MSTATUS_AND_RETURN_IT(err);
                color_val.coordinate.w = 1.0f;

                sm_attr.setAttribute("Albedo", color_val);
            }
            else
            {
                sm.setMaterial("mesh_textured");

                MFnDependencyNode color_file(color_plugs[0].node(), &err);
                CHECK_MSTATUS_AND_RETURN_IT(err);

                MPlug filename = color_file.findPlug("fileTextureName", false, &err);
                CHECK_MSTATUS_AND_RETURN_IT(err);

                MString fnstr;
                err = filename.getValue(fnstr);
                CHECK_MSTATUS_AND_RETURN_IT(err);

                const char* name = strrchr(fnstr.asChar(), '/');
                name = name ? name+1 : fnstr.asChar();

                filename_t fname;
                copy_string_to_fixed(fname.name, sizeof(filename_t().name), name);

                sm_attr.setAttribute("Albedo", fname);
            }

            sm_attr.setAttribute("Specular", spec_int_val);
            sm_attr.setAttribute("Shininess", sh_val);
        }
        else if(stn == "lambert")
        {
            float   spec_int_val = 0.0f,
                    sh_val = 1.0f;

            MPlug color = shader.findPlug("color", false, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);

            MPlugArray color_plugs;
            color.connectedTo(color_plugs, true, false, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            if(color_plugs.length() > 1)
                return MS::kFailure;

            if(color_plugs.length() == 0)
            {
                vec4_t  color_val;

                sm.setMaterial("mesh_simple");

                err = color.child(0).getValue(color_val.coordinate.x);
                CHECK_MSTATUS_AND_RETURN_IT(err);
                err = color.child(1).getValue(color_val.coordinate.y);
                CHECK_MSTATUS_AND_RETURN_IT(err);
                err = color.child(2).getValue(color_val.coordinate.z);
                CHECK_MSTATUS_AND_RETURN_IT(err);
                color_val.coordinate.w = 1.0f;

                sm_attr.setAttribute("Albedo", color_val);
            }
            else
            {
                sm.setMaterial("mesh_textured");

                MFnDependencyNode color_file(color_plugs[0].node(), &err);
                CHECK_MSTATUS_AND_RETURN_IT(err);

                MPlug filename = color_file.findPlug("fileTextureName", false, &err);
                CHECK_MSTATUS_AND_RETURN_IT(err);

                MString fnstr;
                err = filename.getValue(fnstr);
                CHECK_MSTATUS_AND_RETURN_IT(err);

                const char* name = strrchr(fnstr.asChar(), '/');
                name = name ? name+1 : fnstr.asChar();

                filename_t fname;
                copy_string_to_fixed(fname.name, sizeof(filename_t().name), name);

                sm_attr.setAttribute("Albedo", fname);
            }

            sm_attr.setAttribute("Specular", spec_int_val);
            sm_attr.setAttribute("Shininess", sh_val);
        }
        else
            return MS::kFailure;
    }

    return MS::kSuccess;
}

MStatus CarinaMayaTranslator::readSkeletalAnimation(CESMesh& ces_mesh, MObject& skin_obj)
{
    MStatus         err;
    MDagPathArray   joints;
    MFnSkinCluster  fn_skin(skin_obj, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);
    fn_skin.influenceObjects(joints, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);

    MObject root_node,
            current_node,
            parent,
            probable_root;
    for(size_t i = 0; i < joints.length(); ++i)
    {
        current_node = joints[i].node();
        MFnDagNode node(current_node);
        while(node.parentCount())
        {
            node.setObject(current_node);
            if(node.parentCount() > 1)
                return MS::kFailure;
            current_node = node.parent(0);
            if(current_node.apiType() == MFn::kJoint)
                probable_root = current_node;
        }
        if(probable_root != root_node)
        {
            if(root_node.isNull())
                root_node = probable_root;
            else
                return MS::kFailure;
        }
    }

    size_t root_idx;
    for(root_idx = 0;; ++root_idx)
    {
        if(root_idx == joints.length())
            return MS::kFailure;
            
        if(root_node == joints[root_idx].node())
            break;
    }

    err = MAnimControl::setCurrentTime(MTime(0.0f));
    CHECK_MSTATUS_AND_RETURN_IT(err);

    if(joints.length() > ces_max_skel_bones)
        return MS::kFailure;

    DataVector<float>       keyframes;
    MObjectArray            transforms;
    DataVector<bone_child_t> bone_children;
    size_t                  transform_idx = joints.length();
    MPlug bind_pre_matrix = fn_skin.findPlug("bindPreMatrix", false, &err);
    CHECK_MSTATUS_AND_RETURN_IT(err);
    ces_mesh.getSkeleton()->setCount(joints.length(), joints.length());
    for(size_t i = 0; i < joints.length(); ++i)
    {
        assert(joints[i].apiType() == MFn::kJoint);
        MFnTransform joint(joints[i], &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        CHECK_MSTATUS_AND_RETURN_IT(err);

        bone_children.clear();
        for(size_t k = 0; k < joint.childCount(); ++k)
        {
            MDagPath child_path;
            MObject child_obj(joint.child(k));
            MFnDagNode child(child_obj);
            child.getPath(child_path);
            for(size_t j = 0;; ++j)
            {
                if(j == joints.length())
                {
                    MFnDagNode child_node(child_obj);
                    if(child_node.childCount())
                    {
                        transforms.append(child_obj);
                        bone_children.push_back(transform_idx++);
                    }
                    break;
                }
                if(child_path == joints[j])
                {
                    bone_children.push_back(j);
                    break;
                }
            }
        }

        ces_mesh.getSkeleton()->getNode(i).setChildren(bone_children);

        MObject world_matrix_attr = joint.attribute("worldMatrix", &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        MPlug world_matrix_plug(joint.object(), world_matrix_attr);
        assert(world_matrix_plug.numElements() == 1);
        MPlugArray conn_plugs;
        world_matrix_plug[0].connectedTo(conn_plugs, false, true, &err);
        assert(conn_plugs.length() == 1);
        size_t lidx = conn_plugs[0].logicalIndex();
        MPlug joint_bpm = bind_pre_matrix.elementByLogicalIndex(lidx, &err);
        MObject data_obj;
        err = joint_bpm.getValue(data_obj);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        MFnMatrixData bpm(data_obj);
        MatrixToDQ(bpm.matrix(), ces_mesh.getSkeleton()->getNode(i).getInvTransform(0),
                   ces_mesh.getSkeleton()->getNode(i).getInvTransform(1));

        MatrixToDQ(joint.transformationMatrix(), ces_mesh.getSkeleton()->getNode(i).getTransform(0),
                   ces_mesh.getSkeleton()->getNode(i).getTransform(1));

        MPlugArray plugs;
        MAnimUtil::findAnimatedPlugs(joints[i], plugs, false, &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        for(size_t j = 0; j < plugs.length(); ++j)
        {
            MObjectArray anim_curves;
            MAnimUtil::findAnimation(plugs[j], anim_curves, &err);
            CHECK_MSTATUS_AND_RETURN_IT(err);
            MFnAnimCurve anim_curve(anim_curves[0]);
            for(size_t k = 0; k < anim_curve.numKeys(); ++k)
            {
                float key_time = (float)anim_curve.time(k).as(MTime::kSeconds);
                for(size_t m = 0; ; ++m)
                {
                    if(m == keyframes.size())
                    {
                        keyframes.push_back(key_time);
                        break;
                    }
                    if(keyframes[m] == key_time)
                        break;
                    else if(keyframes[m] > key_time)
                    {
                        keyframes.insert(m, key_time);
                        break;
                    }
                }
            }
        }
    }
    ces_mesh.getSkeleton()->setCount(joints.length(), joints.length() + transforms.length());
    for(size_t tpr = 0; tpr != transforms.length(); ++tpr)
    {
        MFnTransform node(transforms[tpr], &err);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        CESBone& transform_node = ces_mesh.getSkeleton()->getNode(tpr + ces_mesh.getSkeleton()->getBoneCount());
        MatrixToDQ(node.transformationMatrix(), transform_node.getTransform(0), transform_node.getTransform(1));
        memcpy(transform_node.getInvTransform(), q_identity_transform, sizeof(q_identity_transform));
        bone_children.clear();
        for(size_t j = 0; j < node.childCount(); ++j)
        {
            MDagPath child_path;
            MObject child_obj(node.child(j));
            MFnDagNode child(child_obj);
            child.getPath(child_path);
            for(size_t k = 0;; ++k)
            {
                if(k == joints.length())
                {
                    transforms.append(child_obj);
                    bone_children.push_back(transform_idx++);
                    break;
                }
                if(child_path == joints[k])
                {
                    bone_children.push_back(k);
                    break;
                }
            }
        }
        ces_mesh.getSkeleton()->getNode(tpr+ces_mesh.getSkeleton()->getBoneCount()).setChildren(bone_children);
    } 

    ces_mesh.getSkeleton()->setRoot(root_idx);

    CESAnimationPtr anim = ces_mesh.addAnimation("Default animation");
    anim->setKeyframes(keyframes);
    for(size_t j = 0; j < keyframes.size(); ++j)
    {
        err = MAnimControl::setCurrentTime(MTime((double)keyframes[j], MTime::kSeconds));
        CHECK_MSTATUS_AND_RETURN_IT(err);
        for(size_t i = 0; i < joints.length(); ++i)
        {
            MFnTransform joint(joints[i], &err);

            CESBoneAnim& bone_anim = anim->getNode(j, i);
            MatrixToDQ(joint.transformationMatrix(), bone_anim.getTransform(0), bone_anim.getTransform(1));
        }
        for(size_t i = 0; i < transforms.length(); ++i)
        {
            MFnTransform transform(transforms[i], &err);

            CESBoneAnim& bone_anim = anim->getNode(j, i + ces_mesh.getSkeleton()->getBoneCount());
            MatrixToDQ(transform.transformationMatrix(), bone_anim.getTransform(0), bone_anim.getTransform(1));
        }
    }

    err = MAnimControl::setCurrentTime(MTime(0.0f));
    CHECK_MSTATUS_AND_RETURN_IT(err);

    return MS::kSuccess;
}

#undef max
void MatrixToDQ(const MMatrix& matrix, float* nd, float* d)
{
    #define COPYSIGN(x, y) y < 0.0f ? -x : x
    nd[3] = sqrt(std::max(0.0f, 1.0f + (float)matrix[0][0] + (float)matrix[1][1] + (float)matrix[2][2]))/2.0f;
    nd[0] = sqrt(std::max(0.0f, 1.0f + (float)matrix[0][0] - (float)matrix[1][1] - (float)matrix[2][2]))/2.0f;
    nd[1] = sqrt(std::max(0.0f, 1.0f - (float)matrix[0][0] + (float)matrix[1][1] - (float)matrix[2][2]))/2.0f;
    nd[2] = sqrt(std::max(0.0f, 1.0f - (float)matrix[0][0] - (float)matrix[1][1] + (float)matrix[2][2]))/2.0f;
    nd[0] = COPYSIGN(nd[0], (float)matrix[1][2] - (float)matrix[2][1]);
    nd[1] = COPYSIGN(nd[1], (float)matrix[2][0] - (float)matrix[0][2]);
    nd[2] = COPYSIGN(nd[2], (float)matrix[0][1] - (float)matrix[1][0]);
    float qtr[4] = { (float)matrix[3][0]/2.0f, (float)matrix[3][1]/2.0f, (float)matrix[3][2]/2.0f, 0.0 };
    d[3] = qtr[3]*nd[3] - qtr[0]*nd[0] - qtr[1]*nd[1] - qtr[2]*nd[2];
    d[0] = qtr[3]*nd[0] + qtr[0]*nd[3] + qtr[1]*nd[2] - qtr[2]*nd[1];
    d[1] = qtr[3]*nd[1] - qtr[0]*nd[2] + qtr[1]*nd[3] + qtr[2]*nd[0];
    d[2] = qtr[3]*nd[2] + qtr[0]*nd[1] - qtr[1]*nd[0] + qtr[2]*nd[3];
}

MStatus CarinaMayaTranslator::readMesh(CESMesh& ces_mesh, MObject& obj)
{
    MStatus err;
    DataVector<CESSkinData>     skin_data;
    DataVector<float>           point_data;
    DataVector<indice_t>        indices;
    DataVector<indice_t>        intermediate_indices;
    MObject                     skin_cluster,
                                mesh_obj;

    MItDependencyGraph dep_iter(obj, 
                                MFn::kSkinClusterFilter,
                                MItDependencyGraph::kUpstream,
                                MItDependencyGraph::kDepthFirst, 
                                MItDependencyGraph::kNodeLevel, &err);
    if(err == MS::kSuccess && !dep_iter.isDone())
    {
        skin_cluster = dep_iter.currentItem();
        err = dep_iter.resetTo(skin_cluster,
                               MFn::kMesh,
                               MItDependencyGraph::kUpstream,
                               MItDependencyGraph::kDepthFirst, 
                               MItDependencyGraph::kNodeLevel);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        if(dep_iter.isDone())
            return MS::kFailure;
        mesh_obj = dep_iter.currentItem();
        err = readSkin(skin_data, skin_cluster, obj);
        CHECK_MSTATUS_AND_RETURN_IT(err);
        err = readSkeletalAnimation(ces_mesh, skin_cluster);
        CHECK_MSTATUS_AND_RETURN_IT(err);
    }
    else
        mesh_obj = obj;

    MIntArray           poly_vert_count,
                        vert_indices,
                        normal_indices,
                        normalids_count,
                        uv_indices,
                        uvid_count;

    // Deindexing
    size_t  triangles,
            current_ind;
    int     triangle_verts[3];
    MFnMesh mesh(mesh_obj);
    mesh.getVertices(poly_vert_count, vert_indices);
    mesh.getNormalIds(normalids_count, normal_indices);
    mesh.getAssignedUVs(uvid_count, uv_indices);

    for(size_t i = 0, vert_processed = 0; i < poly_vert_count.length(); vert_processed += poly_vert_count[i++])
    {
        triangles = poly_vert_count[i] - 2;
        current_ind = intermediate_indices.size();
        intermediate_indices.resize(current_ind + triangles*3);

        for(size_t j = 0; j < triangles; ++j)
        {
            mesh.getPolygonTriangleVertices(i, j, triangle_verts);
            for(size_t k = 0; k < (size_t)poly_vert_count[i]; ++k)
            {
                if(vert_indices[vert_processed + k] == triangle_verts[0])
                    intermediate_indices[current_ind+j*3] = vert_processed + k;
                else if(vert_indices[vert_processed + k] == triangle_verts[1])
                    intermediate_indices[current_ind+j*3+1] = vert_processed + k;
                else if(vert_indices[vert_processed + k] == triangle_verts[2])
                    intermediate_indices[current_ind+j*3+2] = vert_processed + k;
            }
        }
    }

    MFloatPointArray        poly_vertices;
    MFloatVectorArray       poly_normals;
    MFloatArray             poly_u_arr,
                            poly_v_arr;
    CESPointData            point;
    size_t                  data_size = skin_data.empty() ? ces_static_size_float : ces_skinned_size_float;
    
    mesh.getPoints(poly_vertices);
    mesh.getUVs(poly_u_arr, poly_v_arr);
    mesh.getNormals(poly_normals);
    indices.resize(intermediate_indices.size());
    assert(skin_data.empty() || poly_vertices.length() == skin_data.size());
    for(size_t i = 0, j; i < intermediate_indices.size(); ++i)
    {
        size_t  iind = intermediate_indices[i],
                vind = vert_indices[iind],
                nind = normal_indices[iind],
                uvind = uv_indices[iind];
        point.normal.coordinate.x = poly_normals[nind][0];
        point.normal.coordinate.y = poly_normals[nind][1];
        point.normal.coordinate.z = poly_normals[nind][2];
        point.position.coordinate.x = poly_vertices[vind][0]/poly_vertices[vind][3];
        point.position.coordinate.y = poly_vertices[vind][1]/poly_vertices[vind][3];
        point.position.coordinate.z = poly_vertices[vind][2]/poly_vertices[vind][3];
        point.texcoord.coordinate.x = poly_u_arr[uvind];
        point.texcoord.coordinate.y = poly_v_arr[uvind];

        bool point_data_eq = false;
        for(j = 0; j < point_data.size()/data_size; ++j)
        {
            CESPointData& ref_point = reinterpret_cast<CESPointData&>(point_data[j*data_size]);

            point_data_eq = (
                point.normal.coordinate.x == ref_point.normal.coordinate.x &&
                point.normal.coordinate.y == ref_point.normal.coordinate.y &&
                point.normal.coordinate.z == ref_point.normal.coordinate.z &&
                point.position.coordinate.x == ref_point.position.coordinate.x &&
                point.position.coordinate.y == ref_point.position.coordinate.y &&
                point.position.coordinate.z == ref_point.position.coordinate.z &&
                point.texcoord.coordinate.x == ref_point.texcoord.coordinate.x &&
                point.texcoord.coordinate.y == ref_point.texcoord.coordinate.y);
            if(point_data_eq)
                break;
        }
        if(point_data_eq)
            indices[i] = j;
        else
        {
            indices[i] = point_data.size() / data_size;
            float* pd_begin = reinterpret_cast<float*>(&point);
            float* pd_end = reinterpret_cast<float*>(&point) + ces_static_size_float;
            point_data.insert(point_data.end(), pd_begin, pd_end);
            if(!skin_data.empty())
            {
                float* sd_begin = reinterpret_cast<float*>(&skin_data[vind]);
                float* sd_end = reinterpret_cast<float*>(&skin_data[vind]) + ces_skin_size_float;
                point_data.insert(point_data.end(), sd_begin, sd_end);
            }
        }
    }

    ces_mesh.setIndices(indices);
    ces_mesh.setData(point_data);

    err = readMaterial(ces_mesh, poly_vert_count, obj);
    CHECK_MSTATUS_AND_RETURN_IT(err);

    return MS::kSuccess;
}